前言
这篇文章主要分析一下dispatch_group的底层实现,主要是包括几个重要的函数:
- dispatch_group_create
- dispatch_group_enter
- dispatch_group_leave
- dispatch_group_wait
- dispatch_group_notify
- dispatch_group_async
相关API解析
dispatch_group_create
关于这个问题实际上在开篇中提过一次,当时主要是为了分析dispatch_group_s
的本质,而且得知dispatch_group_t
本质上是一个value为LONG_MAX的semaphore,那么可想而知,基本上对dispatch_group_t
的分析,信号量肯定脱不了干系1
2
3
4dispatch_group_t
dispatch_group_create(void){
return (dispatch_group_t)dispatch_semaphore_create(LONG_MAX);
}
关于信号量的底层实现,会有专门的一篇来进行剖析
dispatch_group_enter
1 | void dispatch_group_enter(dispatch_group_t dg){ |
可以看到,首先先把dispatch_group_t
转换为dispatch_semaphore_t
,然后调用dispatch_semaphore_wait
1 | long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout){ |
该函数先把信号量的信号值原子减1,如果大于等于0,则表示有资源可用,直接返回;否则调用_dispatch_semaphore_wait_slow
,等到FIFO队列中信号量的到来,直到timeout为止。关于_dispatch_semaphore_wait_slow
的具体分析请参考dispatch_semaphore篇
调用栈:1
2
3dispatch_group_enter
└──dispatch_semaphore_wait
└──_dispatch_semaphore_wait_slow
dispatch_group_leave
1 | void dispatch_group_leave(dispatch_group_t dg){ |
表明任务组group中一个block任务已经完成,必要时发出唤醒信号。在dispatch_group_leave
中,将group的信号量加1,然后进入判断:如果此时信号值和group信号量的初始值相等,则表明任务完成,进入唤醒流程_dispatch_group_wake
.
1 | long _dispatch_group_wake(dispatch_semaphore_t dsema){ |
在_dispatch_group_wake方法中主要做了两件事:
- 一是通过semaphore_signal唤醒信号量,
- 二是依次调用dispatch_async_f
调用栈:1
2
3
4dispatch_group_leave
└──_dispatch_group_wake
└──semaphore_signal
└──dispatch_async_f
dispatch_group_wait
1 | long dispatch_group_wait(dispatch_group_t dg, dispatch_time_t timeout) |
1 | DISPATCH_NOINLINE |
调用栈:1
2
3
4dispatch_group_wait
└──_dispatch_group_wait_slow
└──semaphore_timedwait
└──semaphore_wait
dispatch_group_notify
1 | void dispatch_group_notify(dispatch_group_t dg, dispatch_queue_t dq, dispatch_block_t db){ |
1 | void dispatch_group_notify_f(dispatch_group_t dg, dispatch_queue_t dq, void *ctxt, void (*func)(void *)){ |
调用栈:1
2
3dispatch_group_notify
└──dispatch_group_notify_f
└──_dispatch_group_wake
dispatch_group_async
先通过如下伪代码直观上理解一下dispatch_group_async
的执行流程
1 | /// 伪代码,理解用 |
实际源码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21void dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq, dispatch_block_t db){
dispatch_group_async_f(dg, dq, _dispatch_Block_copy(db), _dispatch_call_block_and_release);
}
void dispatch_group_async_f(dispatch_group_t dg, dispatch_queue_t dq, void *ctxt, void (*func)(void *)){
dispatch_continuation_t dc;
_dispatch_retain(dg);
dispatch_group_enter(dg);
dc = _dispatch_continuation_alloc_cacheonly() ?: _dispatch_continuation_alloc_from_heap();
dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT|DISPATCH_OBJ_GROUP_BIT);
dc->dc_func = func;
dc->dc_ctxt = ctxt;
dc->dc_group = dg;
_dispatch_queue_push(dq, dc);
}
可以看到,dispatch_group_async
是对dispatch_group_async_f
的一层封装。
而在dispatch_group_async_f
函数中主要执行了以下操作:
- 调用dispatch_group_enter,进行信号量引用计数管理;
- 新建了一个dispatch_continuation_t,记录block任务所属的任务组,相关上下文,调用方法等信息;
- 调用_dispatch_queue_push将dispatch_continuation_t加入队列
大家还记得dispatch_async_f
的实现吗?1
2
3
4
5
6
7
8
9
10
11void dispatch_async_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func)
{
dispatch_continuation_t dc = fastpath(_dispatch_continuation_alloc_cacheonly());
if (!dc) {
return _dispatch_async_f_slow(dq, ctxt, func);
}
dc->do_vtable = (void *)DISPATCH_OBJ_ASYNC_BIT;
dc->dc_func = func;
dc->dc_ctxt = ctxt;
_dispatch_queue_push(dq, dc);
}
可以发现,dispatch_group_async_f
和dispatch_async_f
的实现高度一致,主要的不同在于dispatch_group_async_f
在前面调用了dispatch_group_enter
方法:
到了这里,大家只看到了伪代码的前半部分,但是后半部分在哪里对应呢?
实际上是对应于_dispatch_continuation_pop
里面的操作,注意最后几行代码(这里不会对_dispatch_continuation_pop
作详细分析,只是为了了解)
1 | static inline void _dispatch_continuation_pop(dispatch_object_t dou) |
实际上这个函数,包括里面的_dispatch_queue_push
和_dispatch_continuation_pop
都已经在dispatch_queue篇中分析到了,这里不再重复继续分析下去了。
总结
group篇调用到的很多函数会和queue篇交叉,一些重复的问题可以参考queue篇的分析。